26 某国际电商平台销售数据分析
26.1 引言电商数据的多维度分析
电商平台关键问题:
- 折扣策略: 多大折扣最优?
- 利润分析: 折扣与利润关系
- 品类表现: 哪些品类利润高?
- 定价策略: 如何平衡销量与利润
26.2 本章学习目标
本章围绕”折扣打到几折最划算”这一电商定价问题,练习在聚合口径上做文章。通过本章学习,你将掌握:
- 电商交易数据的读取与冗余字段清理
- 用
nunique检验”每天是否统一折扣”“各品类折扣是否一致”这类业务前提 groupby+agg多指标聚合,并用连除构造”单位时间、单位商品”的平均量指标- 用
query过滤小样本折扣档位,控制结论的代表性 - 用多指标折线图定位折扣-利润关系的拐点与最优折扣区间
26.3 数据准备
任务要求:读取平台内置的 superstore.xlsx 并删除 Postal Code 列;先检验”同一天折扣是否统一”“各品类折扣是否一致”两个前提;再按折扣分组聚合销量、销售额、利润、天数与商品数,构造”单位时间、单位商品”的平均利润、平均销售额、平均销量三个指标;过滤商品种类过少的折扣档位后,绘制三条折线对比不同折扣下的平均表现。
平台任务(平台原始代码)
以下代码与教学平台任务要求完全一致:
# 注:superstore.xlsx 数据文件本地没有,但平台已经内置
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
# 导入必要的包
import pandas as pd # 用于数据处理和分析
import matplotlib.pyplot as plt # 用于绘图
import seaborn as sns # 用于高级绘图
from datetime import datetime # 用于处理日期时间数据
# 读取Excel文件
data = pd.read_excel('superstore.xlsx')
# 删除Postal Code字段
del data['Postal Code']
print(data.head()) # 输出前几行数据
# 折扣与销售利润关系探究
# 判断每一天是不是同一个折扣
huigui01 = data.groupby('Order Date').Discount.nunique()
print(huigui01.head()) # 输出前几行数据
# 判断每个品类的折扣情况
huigui03 = data.groupby(['Sub-Category', 'Discount']).agg({
'Profit': 'sum', # 计算每个子类别和折扣组合的总利润
'Order Date': 'nunique', # 计算每个子类别和折扣组合的唯一订单日期数量
}) # 数据结构定义结束
huigui03['Profit_pd'] = huigui03['Profit'] / huigui03['Order Date'] # 计算单位时间内的平均利润
print(huigui03.head(15)) # 打印前15行结果,检查每个子类别和折扣组合的利润情况
# 通过前面的判断,可以发现:
# 1)同一天不同商品会采取不同的折扣;
# 2)不同品类的商品,折扣力度也并不相同;
# 为了简化问题,我们将所有销售数据按照折扣分组,然后对销量、销售额、利润等数据进行汇总聚合;
# 同时,考虑到折扣促销的本质,就是为了通过降价提高销量,相应单个产品的利润会有所下降,但是单个品类、单位时间的利润总和会得到提升;
# 因此我们在探究折扣与利润之间关系时候,需要剔除品类数量、折扣时间的影响。
# 按折扣分组,计算汇总指标
profit_8discount = data.groupby('Discount').agg({
'Quantity': 'sum', # 计算每个折扣下的总销售数量
'Sales': 'sum', # 计算每个折扣下的总销售额
'Profit': 'sum', # 计算每个折扣下的总利润
'Order Date': 'nunique', # 计算每个折扣下的唯一订单日期数量
'Product Name': 'nunique', # 计算每个折扣下的唯一商品名称数量
}) # 数据结构定义结束
# 重命名列
profit_8discount.columns = ['Quantity_sum', 'Sales_sum', 'Profit_sum', 'Dates_sum', 'Products_sum']
# 计算单位时间内每个商品的平均利润、平均销售额和平均销售数量
profit_8discount['Profit_p_p'] = profit_8discount['Profit_sum'] / profit_8discount['Products_sum'] / profit_8discount['Dates_sum'] # 平均利润
profit_8discount['Sales_p_p'] = profit_8discount['Sales_sum'] / profit_8discount['Products_sum'] / profit_8discount['Dates_sum'] # 平均销售额
profit_8discount['Quantity_p_p'] = profit_8discount['Quantity_sum'] / profit_8discount['Products_sum'] / profit_8discount['Dates_sum'] # 平均销售数量
# 重置索引,方便后续操作
profit_8discount = profit_8discount.reset_index()
print(profit_8discount) # 输出利润数据
# 选取折扣涉及商品种类大于(商品种类总数)*0.005的情形;
profit_8discount22 = profit_8discount.query('Products_sum > 19')
print(profit_8discount22) # 输出利润数据
# 绘制折扣与利润关系的折线图
sns.set(style="darkgrid") # 设置绘图风格为'darkgrid'
plt.figure(figsize=(10, 6)) # 设置画布大小
# 遍历需要绘制的列(平均利润、平均销售额、平均销售数量)
for i in profit_8discount22.columns[6:-2]:
ax = sns.lineplot(x='Discount', y=i, label=i, data=profit_8discount22) # 绘制折线图
# 添加图例和标签
plt.legend() # 显示图例
plt.xlabel('Discount Range') # 设置x轴标签
plt.ylabel('Sales') # 设置y轴标签
plt.savefig("10.png") # 保存图表为PNG文件
plt.show() # 显示图表
# 通过分析发现:
# 1)销售折扣20%(折扣为0.2表示打8折(即降价20%))是销售利润正负的拐点,当折扣继续增加,销售利润基本为负值。
# 2)当折扣为0.002和0.07时,单个品类单位时间的总利润有显著的提升,也就是营销中低折扣的“小刀”策略,可以有效刺激消费者购买欲望,进而提升销量和利润。
# 3)当折扣为0.85、0.55、0.57的时候,利润会出现一些极端的负数值,通过查看对应的商品种类和该折扣销售天数,
# 可以发现此种情况是特殊个例,属于基于特定商品、网红商品的引流活动,对整体商品的定价策略不具备代表性。预期输出:数据文件平台内置,本地无数据,具体数值以平台运行结果为准。判读要点:
- 文本输出:每日折扣种类数的前几行;子品类×折扣组合的利润、天数表前15行;按折扣聚合的指标总表及过滤后的表——重点比较 Profit_p_p 列随 Discount 增大的变化方向与符号。
- 图形输出(10.png):横轴为折扣、纵轴为平均量的三条折线。判读要点:平均利润曲线由正转负的折扣位置(拐点)、低折扣区间的抬升幅度、高折扣端是否出现深谷;三条曲线量纲不同,读形态与拐点,不比较绝对高低。
- 平台原始代码的块尾注释保留了作者的三条判读(20%为利润正负拐点、低折扣”小刀”策略、极端折扣属引流个例),可作读图参照,最终以平台运行结果为准。
下方提供一个本地演练版:由于 superstore.xlsx 仅平台内置,本地以一份 12 行的内联合成迷你表代替(保留平台代码实际用到的字段,取值为演示流程而设的模拟值),并复现本章核心口径的一个变体——统计利润为负的折扣订单占比,并按折扣档位分解:
# 本地演练说明:平台内置的superstore.xlsx本地没有,以下用同结构的内联合成迷你数据代替
import pandas as pd # 导入Pandas数据分析库
mini = pd.DataFrame({ # 构造12行迷你表,列取平台代码实际用到的字段
'Order Date': ['2023-01-03', '2023-01-03', '2023-01-10', '2023-01-15', '2023-01-15', '2023-02-01',
'2023-02-07', '2023-02-07', '2023-02-14', '2023-02-21', '2023-03-01', '2023-03-08'], # 订单日期
'Sub-Category': ['Chairs', 'Tables', 'Phones', 'Chairs', 'Bookcases', 'Tables', 'Phones', 'Chairs', 'Bookcases', 'Tables', 'Phones', 'Chairs'], # 子品类
'Product Name': ['产品A', '产品B', '产品C', '产品A', '产品D', '产品B', '产品E', '产品A', '产品D', '产品F', '产品C', '产品G'], # 商品名称
'Sales': [520.0, 980.0, 340.0, 460.0, 810.0, 1250.0, 390.0, 430.0, 760.0, 1120.0, 410.0, 380.0], # 销售额
'Quantity': [3, 5, 2, 3, 4, 6, 2, 3, 4, 5, 2, 2], # 销量
'Discount': [0.0, 0.2, 0.0, 0.1, 0.3, 0.2, 0.0, 0.5, 0.7, 0.2, 0.0, 0.8], # 折扣(0.2表示降价20%)
'Profit': [180.2, -95.4, 62.1, 120.5, -210.0, -88.0, 75.3, -260.5, -540.2, -102.6, 68.9, -318.4] # 利润
})
disc = mini[mini['Discount'] > 0] # 只看有折扣的订单,与平台任务"折扣与利润关系"的研究对象一致
neg_share = (disc['Profit'] < 0).mean() # 利润为负的折扣订单占比:负利润订单数/折扣订单数
by_discount = disc.groupby('Discount')['Profit'].agg(订单数='size', 负利润订单数=lambda s: (s < 0).sum()) # 按折扣档位汇总
by_discount['负利润订单占比'] = by_discount['负利润订单数'] / by_discount['订单数'] # 各档位内部占比
print('利润为负的折扣订单占比:', round(neg_share, 4)) # 总体占比
print('按折扣档位汇总:')
print(by_discount)预期输出(本机实际运行结果):
利润为负的折扣订单占比: 0.875
按折扣档位汇总:
订单数 负利润订单数 负利润订单占比
Discount
0.1 1 0 0.0
0.2 3 3 1.0
0.3 1 1 1.0
0.5 1 1 1.0
0.7 1 1 1.0
0.8 1 1 1.0
判读要点与平台任务同型:迷你表中折扣订单共 8 笔、其中 7 笔利润为负,总占比 0.875;分档位看,折扣 0.1 的订单利润仍为正,折扣升至 0.2 及以上后全部转负——对应平台任务”20% 折扣是利润正负拐点”的判读。真实数据中各档位订单数远不止 1—3 笔,占比不会如此整齐,这正是平台任务先做 query('Products_sum > 19') 样本量过滤的原因。
# 注:本块是平台任务的分步讲解版(重述读取与清理部分,非新增分析)
# 注:superstore.xlsx 数据文件本地没有,但平台已经内置
# =============================================================================
# 题目:国际电商平台销售数据预处理与清洗
# =============================================================================
# 本代码块实现电商销售数据的读取、清洗和初步探索
# 重点处理缺失值和异常值,为后续分析奠定基础
# ==================== 导入数据分析库 ====================
import pandas as pd # 导入Pandas库,用于数据表操作
import matplotlib.pyplot as plt # 导入Matplotlib库,用于绘图
import seaborn as sns # 导入Seaborn库,用于高级统计绘图
from datetime import datetime # 导入datetime模块,用于日期时间处理
# ==================== 读取电商平台销售数据 ====================
data = pd.read_excel('superstore.xlsx') # 读取Excel格式的销售数据文件
# ==================== 数据清洗:删除缺失值过多的字段 ====================
del data['Postal Code'] # 删除邮政编码字段,因为该字段缺失值较多,对分析贡献有限
print('数据形状:', data.shape) # 输出数据集的行数和列数,了解数据规模
print('\n数据预览:') # 输出标题
print(data.head()) # 显示数据的前5行,初步了解数据结构26.4 折扣与销售利润关系探究
26.4.1 问题1每天是否采用统一折扣?
# 注:本块是平台任务对应部分的分步讲解版(重述平台代码,非新增分析)。该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# ==================== 统计每天的折扣种类数量 ====================
daily_discounts = data.groupby('Order Date').Discount.nunique()
# 按订单日期分组,统计每天有多少种不同的折扣力度
# nunique()计算唯一值的数量,用于评估折扣策略的复杂性
print('每天折扣种类数统计:') # 输出标题
print(daily_discounts.head(10)) # 显示前10天的折扣种类数
print(f'\n平均每天折扣种类数: {daily_discounts.mean():.2f}') # 输出平均每天有几种折扣,保留2位小数
print(f'最大折扣种类数: {daily_discounts.max()}') # 输出折扣种类数最多的天有几种折扣结论: 同一天不同商品会采取不同的折扣
26.4.2 问题2各品类折扣情况
# 注:本块是平台任务对应部分的分步讲解版(重述平台代码,非新增分析)。该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# ==================== 按子品类和折扣分组统计 ====================
category_discount = data.groupby(['Sub-Category', 'Discount']).agg({
'Profit': 'sum', # 对利润求和,计算该品类在该折扣力度下的总利润
'Order Date': 'nunique' # 统计不重复的日期数,计算该折扣持续的天数
})
# ==================== 计算单位时间平均利润 ====================
category_discount['Profit_per_day'] = category_discount['Profit'] / category_discount['Order Date']
# 计算每个品类在每个折扣水平下的日均利润
# 这个指标可以帮助判断哪种折扣力度更有效率
print('子品类折扣分析:') # 输出标题
print(category_discount.head(15)) # 显示前15行结果结论: 不同品类的商品,折扣力度不相同
26.4.3 问题3折扣与利润的总体关系
问题1、问题2确认了”同一天折扣不同、不同品类折扣不同”,平台代码因此把全部订单按折扣档位重新聚合,并用 query('Products_sum > 19') 剔除参与商品过少的档位(阈值19≈商品总数×0.005,具体以平台数据为准)。本节不再逐行重述这段聚合代码,改做一个变式实验:把过滤阈值这一个参数在”平台口径19、放宽到5、收紧到100”三档之间切换,观察保留的折扣档位与利润形态如何变化。实验在一张合成的”按折扣聚合”表上运行(列名与平台 profit_8discount 完全一致,数值量级参照superstore真实量级),请重点观察:三档阈值各保留几个档位、Profit_p_p 首次转负的位置是否移动、以及放宽阈值后极端负值有多深。
# 本地演练说明:superstore.xlsx仅平台内置,以下按平台profit_8discount的同构列名构造合成聚合表
import pandas as pd # 导入Pandas数据分析库
profit_tbl = pd.DataFrame({ # 合成的"按折扣聚合"表,列名与平台profit_8discount完全一致,量级参照superstore真实量级
'Discount': [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8], # 折扣档位(0.2表示降价20%,即打8折)
'Quantity_sum': [20000, 9000, 7000, 3500, 600, 350, 40, 25, 30], # 各档位总销量
'Sales_sum': [600000, 220000, 180000, 90000, 15000, 9000, 1200, 800, 900], # 各档位总销售额
'Profit_sum': [200000, 60000, -10000, -40000, -25000, -30000, -12000, -9000, -8000], # 各档位总利润
'Dates_sum': [400, 350, 330, 300, 200, 150, 30, 12, 10], # 各档位出现的天数
'Products_sum': [1500, 800, 700, 400, 90, 60, 8, 5, 6], # 各档位参与的商品种类数
})
profit_tbl['Profit_p_p'] = profit_tbl['Profit_sum'] / profit_tbl['Products_sum'] / profit_tbl['Dates_sum'] # 与平台同口径:总利润/商品数/天数
print('商品总数合计:', profit_tbl['Products_sum'].sum(), ';平台阈值19 ≈ 商品总数×0.005') # 说明阈值19的由来
for thr in [19, 5, 100]: # 三档阈值:平台口径19、放宽到5、收紧到100
kept = profit_tbl.query('Products_sum > @thr')[['Discount', 'Products_sum', 'Profit_p_p']] # 该阈值下保留的折扣档位
flip = kept.loc[kept['Profit_p_p'] < 0, 'Discount'].min() # 平均利润首次转负的折扣档位
print('=== 阈值 Products_sum > %d:保留 %d 个折扣档位,平均利润首次转负于 %.1f ===' % (thr, len(kept), flip)) # 概览行
print(kept.to_string(index=False)) # 明细:观察哪些档位被留下、极端负值有多深预期输出(本机实际运行结果):
商品总数合计: 3569 ;平台阈值19 ≈ 商品总数×0.005
=== 阈值 Products_sum > 19:保留 6 个折扣档位,平均利润首次转负于 0.2 ===
Discount Products_sum Profit_p_p
0.0 1500 0.333333
0.1 800 0.214286
0.2 700 -0.043290
0.3 400 -0.333333
0.4 90 -1.388889
0.5 60 -3.333333
=== 阈值 Products_sum > 5:保留 8 个折扣档位,平均利润首次转负于 0.2 ===
Discount Products_sum Profit_p_p
0.0 1500 0.333333
0.1 800 0.214286
0.2 700 -0.043290
0.3 400 -0.333333
0.4 90 -1.388889
0.5 60 -3.333333
0.6 8 -50.000000
0.8 6 -133.333333
=== 阈值 Products_sum > 100:保留 4 个折扣档位,平均利润首次转负于 0.2 ===
Discount Products_sum Profit_p_p
0.0 1500 0.333333
0.1 800 0.214286
0.2 700 -0.043290
0.3 400 -0.333333
三档阈值的读数可概括为”拐点稳、量级变”:无论阈值取19、5还是100,平均利润首次转负的折扣档位都停在0.2,平台任务”20%折扣是利润正负拐点”的判读在这份合成数据上对过滤参数不敏感——这类”换一个分析参数、检查结论是否随之改变”的检验,方法上叫敏感性分析。但另外两个读数对阈值高度敏感:放宽到 > 5 后,仅有8种、6种商品参与的0.6与0.8档被放进结果,其 Profit_p_p 深达-50与-133,照此绘图会把整个纵轴拉向极端值,常规折扣区间的形态反而看不清;收紧到 > 100 则只剩4个档位,代表性更强,但0.4及以上折扣的信息全部丢失。可见过滤阈值本身就是口径的一部分:报告”最优折扣区间”时,应同时报告阈值及其依据,而不是只报一条曲线。
26.4.4 可视化分析
# 注:本块是平台任务对应部分的分步讲解版(重述平台代码,非新增分析)。该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# ==================== 设置绘图风格 ====================
sns.set(style='darkgrid') # 使用Seaborn的深色网格风格,使图表更专业
plt.figure(figsize=(10, 6)) # 创建10x6英寸的画布
# ==================== 绘制多条折线对比不同指标 ====================
metrics = ['Profit_p_p', 'Sales_p_p', 'Quantity_p_p'] # 定义要绘制的三个指标
labels = ['平均利润', '平均销售额', '平均销售数量'] # 定义每个指标的中文标签
for metric, label in zip(metrics, labels): # 循环绘制三条曲线
sns.lineplot(
x='Discount', # x轴为折扣力度
y=metric, # y轴为对应的业务指标
label=label, # 设置图例标签
data=profit_8discount22 # 使用上方平台任务代码块过滤后的数据(变量沿用平台任务)
)
plt.legend() # 显示图例,区分不同指标
plt.xlabel('折扣', fontsize=12) # 设置x轴标签
plt.ylabel('数值', fontsize=12) # 设置y轴标签
plt.title('折扣与业务指标关系', fontsize=14) # 设置图表标题
plt.grid(True, alpha=0.3) # 显示网格线,透明度0.3,辅助读图
plt.tight_layout() # 自动调整布局
plt.show() # 显示图表26.5 分析结论
关键发现:
- 利润拐点: 折扣20%是利润正负的拐点
- 折扣<20%: 利润为正
- 折扣>20%: 利润为负
- 最优折扣: 0.2%和7%折扣时,单位时间利润显著提升
- 低折扣”小刀”策略刺激购买
- 提升销量和利润
- 极端折扣: 85%, 55%, 57%出现极端负利润
- 特殊商品的引流活动
- 不具备代表性
26.6 商业建议
- 常规定价: 控制折扣在20%以内
- 促销策略: 小幅折扣(0.2%-7%)效果最佳
- 避免亏损: 谨慎使用高折扣促销
- 差异化定价: 不同品类采用不同折扣策略
26.7 本章小结
本章新增的技能要点(分析结论见上文,此处只列方法):
- 先验证业务前提再聚合:用
nunique检验”每天/每品类折扣是否统一”,其结果决定后续”按折扣直接聚合”是否需要口径修正。 - 双重平均口径的设计:
总利润 / 商品数 / 天数同时剔除参与商品数量与持续时间的影响,是本章最核心的指标构造。 - 样本量过滤:
query('Products_sum > 19')剔除参与商品过少的折扣档位,防止小样本均值把结论带偏。 - 多指标同图对比:三条曲线共用横轴折扣,用于找拐点与形态对照,而非比较绝对数值。
- 聚合前的列名重整:
agg之后立即重命名列并在reset_index后再绘图,是保证后续代码可读的顺序习惯。
易错点:
- 把
Discount=0.2读成”打到2折”:本章口径是降价20%(即打8折),误读会把”折扣控制在20%以内”错放宽成”80%以内”,0.55、0.57、0.85 也会被误当成正常促销区间 - 直接比较各折扣档位的利润总和:总额混入体量因素,得到的是”主流档位总额最高”的规模结论;定价比较须用
Profit_p_p的”总利润/商品数/天数”口径 - 拿 0.55、0.57、0.85 档的极端负利润当一般规律:这些是参与商品极少的引流个例,正是
query('Products_sum > 19')要过滤掉的小样本 - 过滤阈值随手取、不随结论交代:阈值决定哪些档位入图,放宽后小样本档的极端负值会把纵轴拉深、掩盖常规折扣区间的形态
- 三条曲线共用一个纵轴就比较绝对高低:三指标量纲不同,只能读形态与拐点,不能比较曲线之间的数值大小
agg之后跳过列名重整:沿用Order Date这类原列名,query('Products_sum > 19')会因列不存在报错,按列位置切片也会错位
26.8 动手与思考
以下练习每题附参考答案(默认折叠);概念题与变式题可对照自查,商业判断题不设唯一答案,判断依据与口径比结论更重要。
概念辨析:直接比较各折扣档位的利润总和,与比较 Profit_p_p(单位时间、单位商品的平均利润),结论可能为何不同?本章为什么选择后者?
参考答案(点开前请先独立完成)
解题思路:利润总和混入了“体量”因素——无折扣档参与的普通商品多、持续天数长,利润总额天然偏大;深折扣档可能只有少数商品、短期活动,总额自然小。直接比较总额,得到的往往是“主流档位总额最高”这种由体量决定的结论,回答不了“定价打到几折更划算”。Profit_p_p 用“总利润/商品数/天数”的连除(列表 26.1 中平台代码的构造方式),把参与商品数量与持续时间的影响同时剔除,度量的是“同样多的商品、同样长的时间下,哪个折扣档位平均更赚钱”。本章的业务问题是折扣档位之间的定价比较,不是档位之间的规模比较,所以必须用单位化口径;总额口径并非无用,它适合回答“哪个档位贡献了最多利润绝对额”。
回扣本章:对应“本章新增的技能要点”第2条(双重平均口径的设计:总利润/商品数/天数)。
概念辨析:按本章口径,Discount=0.2 表示”降价20%(打8折)“。若有人误读为”打到2折”,会在业务决策上造成什么后果?请结合拐点位置说明。
参考答案(点开前请先独立完成)
解题思路:本章口径下利润正负的拐点在 Discount=0.2,即降价一旦超过两成、单位平均利润基本转负(列表 26.1 块尾注释的判读)。若把 0.2 误读成“打到2折”(降价80%),数值与含义整个翻转:其一,会以为拐点意味着“降价80%才危险”,从而把“折扣控制在20%以内”的结论错放宽成“控制在80%以内”,等于把安全边界放大了四倍;其二,会把 0.55、0.57、0.85 这些在本章口径下已是深度亏损区的档位,误读成“降价四成多、不算深”的正常促销区间,直接对标它们做活动会带来持续亏损;其三,低折扣“小刀”策略(0.002、0.07 这类小幅降价)的刺激作用也会被低估。折扣字段一个符号的口径没对齐,定价决策的方向就可能完全相反,这正是“先验证业务前提再聚合”的第一步。
回扣本章:对应“本章新增的技能要点”第1条(先验证业务前提再聚合,字段含义是最大的前提)。
变式任务:把过滤阈值
Products_sum > 19分别放宽和收紧一档,检验拐点结论是否稳健——这类检验在方法上叫什么、为什么必要?参考答案(点开前请先独立完成)
改造思路:这类检验在方法上叫敏感性分析(sensitivity analysis)——换一个分析参数(此处是样本量过滤阈值),检查结论是否随之改变。它必要是因为过滤阈值本身就是口径的一部分:阈值决定哪些折扣档位有资格进入图表,若拐点位置对阈值高度敏感,“最优折扣区间”的结论就只在特定阈值下成立。正文已经做过这一实验(列表 26.6,在合成聚合表上把阈值在19、5、100三档切换),结论可概括为“拐点稳、量级变”:三档阈值下平均利润首次转负的折扣档位都停在0.2,但保留的档位数与极端负值的深度对阈值高度敏感。变式代码把阈值参数化后循环:
# 变式代码:阈值敏感性检验——聚合口径不变,仅切换query中的阈值参数 profit_8discount = data.groupby('Discount').agg({ # 与平台任务一致的聚合 'Quantity': 'sum', 'Sales': 'sum', 'Profit': 'sum', # 三个总量指标 'Order Date': 'nunique', 'Product Name': 'nunique' # 天数与商品种类数 }) profit_8discount.columns = ['Quantity_sum', 'Sales_sum', 'Profit_sum', 'Dates_sum', 'Products_sum'] # 与平台任务一致的列名重整 profit_8discount['Profit_p_p'] = profit_8discount['Profit_sum'] / profit_8discount['Products_sum'] / profit_8discount['Dates_sum'] # 与平台任务一致的双重平均口径 profit_8discount = profit_8discount.reset_index() # 与平台任务一致 for threshold in [19, 5, 100]: # ← 改动点:平台口径19、放宽到5、收紧到100三档切换 kept = profit_8discount.query('Products_sum > @threshold') # ← 改动点:阈值由常量19换成循环变量 flip = kept.loc[kept['Profit_p_p'] < 0, 'Discount'].min() # 平均利润首次转负的折扣档位 print('阈值>%d:保留%d个档位,Profit_p_p首次转负于%s' % (threshold, len(kept), flip)) # 三档读数并排输出结构性判读:输出由三行概览组成,每行报告一个阈值下保留的档位数与首次转负位置。判读要点:若三行的“首次转负位置”相同,拐点结论对该阈值稳健;若保留档位数或极端负值深度随阈值大幅变化,报告“最优折扣区间”时必须同时报告阈值及其依据,而不是只报一条曲线。
预期输出:数据文件由教学平台内置,本机无法复现,具体数值以平台运行结果为准。
注意:以上为本题变式的独立代码;列表 26.1 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。
回扣本章:对应“本章新增的技能要点”第3条(样本量过滤防止小样本均值带偏结论,阈值怎么取同样是口径)。
变式任务:若要按品类(Sub-Category)分别绘制”折扣-平均利润”曲线,代码结构需要怎样扩展?预计会遇到的读图难点是什么?
参考答案(点开前请先独立完成)
改造思路:平台代码其实已经算出了“子品类×折扣”的聚合(列表 26.1 中的
huigui03,含各组合的总利润与天数),扩展分三步:在该表上补一列单位时间平均利润;reset_index()把子品类与折扣从索引还原为普通列(这是能按列绘图的前提);再按子品类循环绘制折线、每品类一条曲线用图例区分(品类多时可改用sns.relplot(col='Sub-Category', col_wrap=4)分面成小图矩阵)。# 变式代码:按子品类分别绘制“折扣-平均利润”曲线(在平台已有的子品类聚合上扩展) category_discount = data.groupby(['Sub-Category', 'Discount']).agg({'Profit': 'sum', 'Order Date': 'nunique'}) # 与平台任务huigui03同型 category_discount['Profit_pd'] = category_discount['Profit'] / category_discount['Order Date'] # 与平台任务一致:单位时间平均利润 category_discount = category_discount.reset_index() # ← 新增:把子品类与折扣从索引还原为普通列,便于按列绘图 sns.set(style='darkgrid') # 与平台任务一致 for sub_category, sub_table in category_discount.groupby('Sub-Category'): # ← 新增:按子品类逐一循环 sns.lineplot(x='Discount', y='Profit_pd', data=sub_table, label=sub_category) # ← 新增:每个子品类一条曲线 plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left') # ← 新增:图例外置,防止长品类名遮挡曲线 plt.xlabel('Discount Range') # 与平台任务一致 plt.ylabel('Profit per day') # ← 改动点:轴标签换成本变式的指标名结构性判读:图形由“一张图三条平均量曲线”变为“一张图多条品类曲线(或多幅分面小图)”,横轴仍是折扣。读图难点有三:其一,子品类数量多,同图曲线容易拥挤,色系与图例要精心安排;其二,各品类体量悬殊,单位时间利润的量纲不可比,曲线高低不能直接当“谁更赚钱”读,只能读各自随折扣的形态与拐点;其三,小样本品类的曲线锯齿大,个别深谷可能是少数订单造成的,应对照该品类的样本量再下判断(与平台任务过滤小样本档位是同一个道理)。
预期输出:数据文件由教学平台内置,本机无法复现,具体数值以平台运行结果为准。
注意:以上为本题变式的独立代码;列表 26.1 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。
回扣本章:对应“本章新增的技能要点”第5条(
agg之后重整列名、reset_index()之后再绘图,是保证代码可读的顺序习惯)。商业判断:运营团队计划对标平台结果中极端折扣档位(0.55、0.57、0.85,以平台运行结果为准)对个别网红商品做引流活动。依据本章口径,你支持还是反对?决策前还应补充哪些数据?
参考答案(点开前请先独立完成)
参考作答框架:本题不设唯一结论,优秀作答应覆盖以下维度。
- 口径先行:平台结果显示的这几个档位处于极端负利润,但注意它们正是被平台代码
query('Products_sum > 19')过滤掉的小样本档位(列表 26.1 块尾注释也判断其为引流个例、不具代表性);且 Profit_p_p 是“单位时间、单位商品”的口径,不是活动整体损益的口径。 - 分析维度:引流活动的目标函数不是该商品自身的利润,而是流量、新客与连带销售——判断依据应是“毛利牺牲+活动成本”对比“新客获取成本节省+连带客单价提升+复购生命周期价值”,而不是单看该档位的平均利润符号。
- 风险考量:深度折扣的价格锚定效应(拉低消费者对该商品乃至品类的心理价位)、羊毛党套利、亏损常态化(一次活动被要求无限续期)、平台补贴与合规规则变动。
- 模型边界:聚合口径看不出单客行为,历史相关性不等于因果;0.55、0.85 档位的负利润来自极少数商品,换一个网红商品未必复现。
- 还需补充的数据:引流商品订单的连带购买率与整体客单价变化、活动带来新客的复购率与留存曲线、活动前后毛利与退货率对比、竞品同款价格、该商品的历史价格弹性。
- 常见错误作答形态:一票否决——“平均利润为负所以不能做”(口径错配);或直接照搬0.55、0.85的折扣深度当成“平台验证过的成功经验”;只算该商品的账不算连带与复购的账。
回扣本章:对应“本章新增的技能要点”第3条(小样本档位的均值不具代表性,引流个例正是被过滤的那类样本)。
- 口径先行:平台结果显示的这几个档位处于极端负利润,但注意它们正是被平台代码